#include "fsshell.strings"
#include "fs.h"

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define LINE_LENGTH 1024

#ifdef USE_COLORS
#define DIR_CONTROL_STRING "\e[34m%-40s\e[0m\e[32m%d\e[0m"
#define FILE_CONTROL_STRING "%-40s\e[32m%d\e[0m"
#else
#define DIR_CONTROL_STRING "[%s]:%d"
#define FILE_CONTROL_STRING "%s:%d"
#endif /* USE_COLORS */

char* abspath(char* path);
void e(char* error_string);
int streq(const char* s1, const char* s2);

void cmd_cat(char** args);
void cmd_cd(char** args);
void cmd_cp(char** args);
void cmd_exit(char** args);
void cmd_fetch(char** args);
void cmd_help(char** args);
void cmd_ls(char** args);
void cmd_mkdir(char** args);
void cmd_mount(char** args);
void cmd_mv(char** args);
void cmd_rm(char** args);
void cmd_rmdir(char** args);
void cmd_touch(char** args);
void cmd_umount(char** args);
void cmd_write(char** args);

char cwd[1024] = "/";

int main(int argc, char** argv)
{
  char line[LINE_LENGTH];
  char* cmd;
  char** arg;
  char* args[256];
  char* the_dir;

  if (argc != 1) {
    fprintf(stderr, "Usage: %s\n", argv[0]);
    exit(1);
  }
  for (;;) {
    the_dir = strrchr(cwd, '/') + 1;
    if (!*the_dir) the_dir = "/";
    printf("(%s) > ", the_dir);
    if (!fgets(line, LINE_LENGTH, stdin)) exit(0);
    cmd = strtok(line, " \t\n");
    if (!cmd) continue;
    arg = args;
    while (*arg = strtok(NULL, " \t\n")) ++arg;
    if (streq(cmd, "cat")) cmd_cat(args);
    else if (streq(cmd, "cd")) cmd_cd(args);
    else if (streq(cmd, "cp")) cmd_cp(args);
    else if (streq(cmd, "exit")) cmd_exit(args);
    else if (streq(cmd, "fetch")) cmd_fetch(args);
    else if (streq(cmd, "help")) cmd_help(args);
    else if (streq(cmd, "ls")) cmd_ls(args);
    else if (streq(cmd, "mkdir")) cmd_mkdir(args);
    else if (streq(cmd, "mount")) cmd_mount(args);
    else if (streq(cmd, "mv")) cmd_mv(args);
    else if (streq(cmd, "rm")) cmd_rm(args);
    else if (streq(cmd, "rmdir")) cmd_rmdir(args);
    else if (streq(cmd, "touch")) cmd_touch(args);
    else if (streq(cmd, "umount")) cmd_umount(args);
    else if (streq(cmd, "write")) cmd_write(args);
    else e("No such command.");
  }
}

void cmd_cat(char** args) {
  int fd;
  char buf[256];
  int nbyte;
  if (!*args) return e("Usage: cat file...");
  while (*args) {
    if ((fd = sd_open(abspath(*args), 0)) == -1) {
      e("Couldn't open file.");
      args++;
      continue;
    }
    while ((nbyte = sd_read(fd, buf, 256)) > 0) {
      fwrite(buf, 1, nbyte, stdout);
    }
    if (nbyte != 0) e("Couldn't read the whole file.");
    if (sd_close(fd)) e("Couldn't close file.");
    args++;
  }
}

void cmd_cd(char** args) {
  sd_info_t* nfo;
  if (!*args && *(args+1)) return e("Usage: cd [directory]");
  if (!*args) {
    printf("Current directory: %s\n", cwd);
    return;
  }
  nfo = sd_info(abspath(*args));
  if (nfo && nfo->type == SD_DIRTYPE) {
    strcpy(cwd, abspath(*args));
  }
  else e("No such directory.");
}

void cmd_cp(char** args) {
  int fromfd, tofd;
  char buf[256];
  int nbyte;
  if (!*args || !*(args+1) || *(args+2)) 
    return e("Usage: cp from_file to_file");
  if (streq(*args, *(args+1))) return e("Same file."); /* TODO: cp ./fil fil */
  fromfd = sd_open(abspath(*args), 0);
  if (fromfd == -1) return e("Couldn't open file for reading.");
  tofd = sd_open(abspath(*(args+1)), 1);
  if (tofd == -1) return e("Couldn't open file for writing.");

  while ((nbyte = sd_read(fromfd, buf, 256)) > 0) {
    if (sd_write(tofd, buf, nbyte) != nbyte) {
      e("Couldn't write to file.");
      nbyte = 0; /* Avoid error message below. */
      break;
    }
  }

  if (nbyte != 0) e("Couldn't read the whole file.");

  if ((sd_close(fromfd) == -1) || (sd_close(tofd) == -1))
    e("Couldn't close file.");
}

void cmd_exit(char** args) {
  if (*args) e("Usage: exit");
  else exit(0);
}

void cmd_fetch(char** args) {
  int fromfd, tofd;
  char buf[256];
  int nbyte;
  if (!*args || !*(args+1) || *(args+2)) 
    return e("Usage: fetch from_file to_file");
  fromfd = open(abspath(*args), O_RDONLY);
  if (fromfd == -1) return e("Couldn't open file for reading.");
  tofd = sd_open(abspath(*(args+1)), 1);
  if (tofd == -1) return e("Couldn't open file for writing.");

  while ((nbyte = read(fromfd, buf, 256)) > 0) {
    if (sd_write(tofd, buf, nbyte) != nbyte) {
      e("Couldn't write to file.");
      nbyte = 0; /* Avoid error message below. */
      break;
    }
  }

  if (nbyte != 0) e("Couldn't read the whole file.");

  if ((close(fromfd) == -1) || (sd_close(tofd) == -1))
    e("Couldn't close file.");
}

void cmd_help(char** args) {
  if (!*args) puts(help_all);
  else if (streq(*args, "cat")) puts(help_cat);
  else if (streq(*args, "cd")) puts(help_cd);
  else if (streq(*args, "cp")) puts(help_cp);
  else if (streq(*args, "exit")) puts(help_exit);
  else if (streq(*args, "fetch")) puts(help_fetch);
  else if (streq(*args, "help")) puts(help_help);
  else if (streq(*args, "ls")) puts(help_ls);
  else if (streq(*args, "mkdir")) puts(help_mkdir);
  else if (streq(*args, "mount")) puts(help_mount);
  else if (streq(*args, "mv")) puts(help_mv);
  else if (streq(*args, "rm")) puts(help_rm);
  else if (streq(*args, "rmdir")) puts(help_rmdir);
  else if (streq(*args, "touch")) puts(help_touch);
  else if (streq(*args, "umount")) puts(help_umount);
  else if (streq(*args, "write")) puts(help_write);
  else e("Unknown argument");
}

void cmd_ls(char** args) {
  int long_listing = 0;
  char* dir;
  char** files;
  sd_info_t* nfo;
  char aFilename[1024];
  if (*args && streq(*args, "-l")) {
    long_listing = 1;
    args++;
  }
  if (*args && *(args+1)) return e("Usage: ls [-l] [directory]");
  if (*args) dir = abspath(*args);
  else dir = cwd;
  files = sd_readdir(dir);
  if (!files) return e("Couldn't get filelisting.");
#ifdef INVERT_LONG_LS
  long_listing = !long_listing;
#endif /* INVERT_LONG_LS */
  if (!long_listing) while (*files) puts(*files++);
  else while (*files) {
    sprintf(aFilename, "%s/%s", dir, *files);
    nfo = sd_info(aFilename);
    if (!nfo) {
      e("Couldn't get fileinfo.");
      files++;
      continue;
    }
    if (nfo->type == SD_DIRTYPE) {
      printf(DIR_CONTROL_STRING"\n", *files++, nfo->size);
    }
    else {
      printf(FILE_CONTROL_STRING"\n", *files++, nfo->size);
    }
  }
}

void cmd_mkdir(char** args) {
  if (!*args) {
    e("Usage: mkdir directory...");
    return;
  }
  while (*args) {
    if (sd_mkdir(abspath(*args))) e("Couldn't create directory.");
    args++;
  }
}

void cmd_mount(char** args) {
  if (!*args || !*(args+1) || *(args+2))
    return e("Usage: mount device mount_point");
  if (sd_mount(*args, abspath(*(args+1)))) e("Couldn't mount.");
}

void cmd_mv(char** args) {
  char* from;
  if (!*args || !*(args+1) || *(args+2))
    return e("Usage: mv from_file to_file");
  from = strdup(abspath(*args));
  if (sd_rename(from, abspath(*(args+1))))
    e("Couldn't move file.");
  free(from);
}

void cmd_rm(char** args) {
  if (!*args) return e("Usage: rm filename...");
  while (*args) {
    if (sd_rmfile(abspath(*args))) e("Couldn't remove file.");
    args++;
  }
}

void cmd_rmdir(char** args) {
  if (!*args) return e("Usage: rmdir directory...");
  while (*args) {
    if (sd_rmdir(abspath(*args))) e("Couldn't remove directory.");
    args++;
  }
}

void cmd_touch(char** args) {
  int fd;
  if (!*args) return e("Usage: touch file...");
  while (*args) {
    if ((fd = sd_open(abspath(*args), 1)) == -1) e("Couldn't create file.");
    else if (sd_close(fd)) e("Couldn't close file.");
    args++;
  }
}

void cmd_umount(char** args) {
  if (!*args || *(args+1)) return e("Usage: umount mount_point");
  if (sd_umount(abspath(*args))) e("Couldn't unmount.");
}

void cmd_write(char** args) {
  char line[LINE_LENGTH];
  int fd;
  int nbyte;
  if (!*args || *(args+1)) return e("Usage: write filename");
  if ((fd = sd_open(abspath(*args), 1)) == -1)
    return e("Couldn't open file.");
  while (fgets(line, LINE_LENGTH, stdin)) {
    if (streq(line, ".\n")) break;
    nbyte = strlen(line);
    if (sd_write(fd, line, nbyte) != nbyte) {
      e("Couldn't write to file.");
      break;
    }
  }
  if (sd_close(fd) == -1) e("Couldn't close file.");
}

char* abspath(char* path) {
  /* TODO: Add feature: ..-folding.
   * TODO: Add feature: /-folding.
   * It's nice not to have this when debugging your filesystem. */
  static char newpath[1024];
  if (path[0] == '/') return path;
  sprintf(newpath, "%s/%s", cwd, path);
  return newpath;
}

void e(char* error_string) {
  fprintf(stderr, "%s\n", error_string);
}

int streq(const char* s1, const char* s2) {
  while (*s1) {
    if (*s1 != *s2) return 0;
    ++s1;
    ++s2;
  }
  if (*s2 != '\0') return 0;
  return 1;
}
